package net.homeip.jcperera.nu.core.utils;

import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import net.homeip.jcperera.nu.core.CoreBase;

/**
 * Provides utility functions related to date/time manipulation
 * 
 * @name Date and Time Utility class
 * @author julio.c.perera@gmail.com
 * @application nu
 * @since 0.1.0.0@
 */

public class DateUtil extends CoreBase implements Serializable
{
    // Required to implement Serializable interface
    private static final long   serialVersionUID     = 4541031988218328382L;

    // CoreBase properties, for easy use from other components, as well as logging
    public static final int     runtimeLevel         = 0;
    public static final String  moduleName           = "core";
    public static final String  componentName        = "nu_core_utils_date_util";

    /**
     * Periodicity expressed in milliseconds.
     */
    public enum Periodicity {

        MILLISECOND(1),
        SECOND(1000),
        MINUTE(60000),
        HOUR(3600000),
        DAY(86400000),
        WEEK(604800000),
        Q30DAYS(2592000000l),
        Q365DAYS(31536000000l);

        private long value;

        Periodicity(long value) {
            this.value = value;
        }

        public long toLong() {
            return value;
        }
    }
    
    /**
     * All minutes have this many milliseconds except the last minute of the day on a day defined with a leap second.
     */
    public static final long    MILLISECS_PER_MINUTE = 60 * 1000;

    /**
     * Number of milliseconds per hour, except when a leap second is inserted.
     */
    public static final long    MILLISECS_PER_HOUR   = 60 * MILLISECS_PER_MINUTE;

    /**
     * Number of leap seconds per day expect on
     * <ul>
     * <li>1. days when a leap second has been inserted, e.g. 1999 JAN 1.</li>
     * <li>2. Daylight-savings "spring forward" or "fall back" days.</li>
     * </ul>
     */
    protected static final long MILLISECS_PER_DAY    = 24 * MILLISECS_PER_HOUR;

    /***************************************************************************
     * <p>
     * Value to add to the day number returned by this calendar to find the Julian Day number. This is the Julian Day
     * number for 1/1/1970.
     * </p>
     * <p>
     * <b>Note:</b> Since the unix Day number is the same from local midnight to local midnight adding JULIAN_DAY_OFFSET
     * to that value results in the chronologist, historians, or calenderists Julian Day number.
     * </p>
     * 
     * @see http://www.hermetic.ch/cal_stud/jdn.htm
     */
    public static final long    EPOCH_UNIX_ERA_DAY   = 2440588L;

    /**
     * @param millisecondTime
     *            - time as a binary Unix/Java time value.
     * @see java.util.Calendar
     */
    public static Calendar getCalendarFromMilliseconds(long millisecondTime)
    {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(millisecondTime);
        return calendar;
    }

    /**
     * @see java.util.Calendar#Calendar(int, int, int)
     */
    public static Calendar getCalendarFromYMD(int y, int m, int d)
    {
        Calendar calendar = Calendar.getInstance();
        calendar.set(y, m, d);
        return calendar;
    }

    /**
     * @see java.util.GregorianCalendar#GregorianCalendar(int, int, int, int, int, int)
     */
    public static Calendar getCalendarFromYMDHMS(int y, int m, int d, int h, int min, int s)
    {
        Calendar calendar = Calendar.getInstance();
        calendar.set(y, m, d, h, min, s);
        return calendar;
    }

    /**
     * Returns a {@link Calendar} setted to the date/time of this Calendar according to the {@link SimpleDateFormat}
     * specified format specified
     */
    public static Calendar getCalendarFromStringInFormat(String date, String format) throws ParseException
    {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        calendar.setTime(sdf.parse(date));
        return calendar;
    }

    /**
     * Converts the internal representation of the provided {@link Calendar} to a {@link String} using the
     * {@link SimpleDateFormat} String specified.
     * 
     * @see SimpleDateFormat#format(java.util.Date)
     * @param calendar
     *            The {@link Calendar} to get the representation using the single date format
     * @param format
     *            The {@link SimpleDateFormat} String to convert the representation
     * @return The {@link String} representation using specified simple format
     */
    public static String getUsingSimpleDateFormat(Calendar calendar, String format)
    {
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.format(calendar.getTime());
    }

    /**
     * Converts the current date (by creating a new {@link Date}) to a {@link String} using the
     * {@link SimpleDateFormat} String specified.
     * 
     * @see SimpleDateFormat#format(java.util.Date)
     * @param format
     *            The {@link SimpleDateFormat} String to generate the representation
     * @return The {@link String} representation using specified simple format
     */
    public static String getNowUsingSimpleDateFormat(String format)
    {
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.format(new Date());
    }

    /**
     * @return Day number where day 0 is 1/1/1970, as per the Unix/Java date/time epoch.
     */
    public static long getUnixDay(Calendar calendar)
    {
        long offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
        long day = (long) Math.floor((double) (calendar.getTime().getTime() + offset) / (MILLISECS_PER_DAY));
        return day;
    }

    /**
     * @return LOCAL Chronologists Julian day number each day starting from midnight LOCAL TIME.
     * @see http://tycho.usno.navy.mil/mjd.html for more information about local C-JDN
     */
    public static long getJulianDay(Calendar calendar)
    {
        return getUnixDay(calendar) + EPOCH_UNIX_ERA_DAY;
    }

    /**
     * <p>
     * Find the number of days from the given start date to the given end date. Later end dates result in positive
     * values.
     * </p>
     * <p>
     * Note this is not the same as subtracting day numbers. Just after midnight subtracted from just before midnight is
     * 0 days for this method while subtracting day numbers would yields 1 day.
     * </p>
     * 
     * @param start
     *            - any Calendar representing the moment of time at the start of the interval for calculation.
     * @param end
     *            - any Calendar representing the moment of time at the end of the interval for calculation.
     */
    public static long diffDayPeriods(Calendar start, Calendar end)
    {
        long endL = end.getTimeInMillis() + end.getTimeZone().getOffset(end.getTimeInMillis());
        long startL = start.getTimeInMillis() + start.getTimeZone().getOffset(start.getTimeInMillis());
        return (endL - startL) / MILLISECS_PER_DAY;
    }
}
